package com.hexgon.plugins.rabbitmq;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.amqp.core.*;
import org.springframework.amqp.rabbit.annotation.*;
import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.amqp.rabbit.listener.*;
import org.springframework.amqp.rabbit.listener.adapter.ReplyPostProcessor;
import org.springframework.amqp.rabbit.listener.api.RabbitListenerErrorHandler;
import org.springframework.aop.framework.Advised;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.*;
import org.springframework.beans.factory.config.*;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.expression.StandardBeanExpressionResolver;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.core.task.TaskExecutor;
import org.springframework.lang.Nullable;
import org.springframework.messaging.handler.annotation.support.DefaultMessageHandlerMethodFactory;
import org.springframework.messaging.handler.annotation.support.MessageHandlerMethodFactory;
import org.springframework.messaging.handler.invocation.InvocableHandlerMethod;
import org.springframework.util.*;

import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

/**
 * MultiRabbitListenerAnnotationBeanPostProcessor
 * @author: XuZhibin
 * @date: 2023/11/12 11:17
 * @version: v1.0
 * @attention: 改造@RabbitListener#containerFactory()规则，适配多RabbitMQ源
 */
public class MultiRabbitListenerAnnotationBeanPostProcessor implements BeanPostProcessor, Ordered, BeanFactoryAware, BeanClassLoaderAware, EnvironmentAware, SmartInitializingSingleton {

    public static final String DEFAULT_RABBIT_LISTENER_CONTAINER_FACTORY_BEAN_NAME = "rabbitListenerContainerFactory";
    public static final String RABBIT_EMPTY_STRING_ARGUMENTS_PROPERTY = "spring.rabbitmq.emptyStringArguments";
    private static final ConversionService CONVERSION_SERVICE = new DefaultConversionService();
    private final Log logger = LogFactory.getLog(this.getClass());
    private final Set<String> emptyStringArguments = new HashSet();
    private RabbitListenerEndpointRegistry endpointRegistry;
    private String defaultContainerFactoryBeanName = "rabbitListenerContainerFactory";
    private BeanFactory beanFactory;
    private ClassLoader beanClassLoader;
    private final MultiRabbitListenerAnnotationBeanPostProcessor.RabbitHandlerMethodFactoryAdapter messageHandlerMethodFactory = new MultiRabbitListenerAnnotationBeanPostProcessor.RabbitHandlerMethodFactoryAdapter();
    private final RabbitListenerEndpointRegistrar registrar = new RabbitListenerEndpointRegistrar();
    private final AtomicInteger counter = new AtomicInteger();
    private final ConcurrentMap<Class<?>, MultiRabbitListenerAnnotationBeanPostProcessor.TypeMetadata> typeCache = new ConcurrentHashMap();
    private BeanExpressionResolver resolver = new StandardBeanExpressionResolver();
    private BeanExpressionContext expressionContext;
    private int increment;
    private Charset charset;
    private ConfigurableEnvironment configurableEnvironment;



    public int getOrder() {
        return 2147483647;
    }

    public MultiRabbitListenerAnnotationBeanPostProcessor() {
        this.charset = StandardCharsets.UTF_8;
        this.emptyStringArguments.add("x-dead-letter-exchange");
    }

    public void setEndpointRegistry(RabbitListenerEndpointRegistry endpointRegistry) {
        this.endpointRegistry = endpointRegistry;
    }

    public void setContainerFactoryBeanName(String containerFactoryBeanName) {
        this.defaultContainerFactoryBeanName = containerFactoryBeanName;
    }

    public void setMessageHandlerMethodFactory(MessageHandlerMethodFactory messageHandlerMethodFactory) {
        this.messageHandlerMethodFactory.setMessageHandlerMethodFactory(messageHandlerMethodFactory);
    }

    public void setBeanFactory(BeanFactory beanFactory) {
        this.beanFactory = beanFactory;
        if (beanFactory instanceof ConfigurableListableBeanFactory) {
            this.resolver = ((ConfigurableListableBeanFactory)beanFactory).getBeanExpressionResolver();
            this.expressionContext = new BeanExpressionContext((ConfigurableListableBeanFactory)beanFactory, (Scope)null);
        }

    }

    public void setBeanClassLoader(ClassLoader classLoader) {
        this.beanClassLoader = classLoader;
    }

    public void setEnvironment(Environment environment) {
        String property = (String)environment.getProperty("spring.rabbitmq.emptyStringArguments", String.class);
        if (property != null) {
            this.emptyStringArguments.addAll(StringUtils.commaDelimitedListToSet(property));
        }
        /**
         * 拓展获取配置属性
         * @author xuzhibin
         */
        this.configurableEnvironment = (ConfigurableEnvironment) environment;
    }

    public void setCharset(Charset charset) {
        this.charset = charset;
    }

    MessageHandlerMethodFactory getMessageHandlerMethodFactory() {
        return this.messageHandlerMethodFactory;
    }

    public void afterSingletonsInstantiated() {
        this.registrar.setBeanFactory(this.beanFactory);
        if (this.beanFactory instanceof ListableBeanFactory) {
            Map<String, RabbitListenerConfigurer> instances = ((ListableBeanFactory)this.beanFactory).getBeansOfType(RabbitListenerConfigurer.class);
            Iterator var2 = instances.values().iterator();

            while(var2.hasNext()) {
                RabbitListenerConfigurer configurer = (RabbitListenerConfigurer)var2.next();
                configurer.configureRabbitListeners(this.registrar);
            }
        }

        if (this.registrar.getEndpointRegistry() == null) {
            if (this.endpointRegistry == null) {
                Assert.state(this.beanFactory != null, "BeanFactory must be set to find endpoint registry by bean name");
                this.endpointRegistry = (RabbitListenerEndpointRegistry)this.beanFactory.getBean("org.springframework.amqp.rabbit.config.internalRabbitListenerEndpointRegistry", RabbitListenerEndpointRegistry.class);
            }

            this.registrar.setEndpointRegistry(this.endpointRegistry);
        }

        if (this.defaultContainerFactoryBeanName != null) {
            this.registrar.setContainerFactoryBeanName(this.defaultContainerFactoryBeanName);
        }

        MessageHandlerMethodFactory handlerMethodFactory = this.registrar.getMessageHandlerMethodFactory();
        if (handlerMethodFactory != null) {
            this.messageHandlerMethodFactory.setMessageHandlerMethodFactory(handlerMethodFactory);
        }

        this.registrar.afterPropertiesSet();
        this.typeCache.clear();
    }

    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        Class<?> targetClass = AopUtils.getTargetClass(bean);
        MultiRabbitListenerAnnotationBeanPostProcessor.TypeMetadata metadata = (MultiRabbitListenerAnnotationBeanPostProcessor.TypeMetadata)this.typeCache.computeIfAbsent(targetClass, this::buildMetadata);
        MultiRabbitListenerAnnotationBeanPostProcessor.ListenerMethod[] var5 = metadata.listenerMethods;
        int var6 = var5.length;

        for(int var7 = 0; var7 < var6; ++var7) {
            MultiRabbitListenerAnnotationBeanPostProcessor.ListenerMethod lm = var5[var7];
            RabbitListener[] var9 = lm.annotations;
            int var10 = var9.length;

            for(int var11 = 0; var11 < var10; ++var11) {
                RabbitListener rabbitListener = var9[var11];
                this.processAmqpListener(rabbitListener, lm.method, bean, beanName);
            }
        }

        if (metadata.handlerMethods.length > 0) {
            this.processMultiMethodListeners(metadata.classAnnotations, metadata.handlerMethods, bean, beanName);
        }

        return bean;
    }

    private MultiRabbitListenerAnnotationBeanPostProcessor.TypeMetadata buildMetadata(Class<?> targetClass) {
        Collection<RabbitListener> classLevelListeners = this.findListenerAnnotations(targetClass);
        boolean hasClassLevelListeners = classLevelListeners.size() > 0;
        List<MultiRabbitListenerAnnotationBeanPostProcessor.ListenerMethod> methods = new ArrayList();
        List<Method> multiMethods = new ArrayList();
        ReflectionUtils.doWithMethods(targetClass, (method) -> {
            Collection<RabbitListener> listenerAnnotations = this.findListenerAnnotations(method);
            if (listenerAnnotations.size() > 0) {
                methods.add(new MultiRabbitListenerAnnotationBeanPostProcessor.ListenerMethod(method, (RabbitListener[])listenerAnnotations.toArray(new RabbitListener[listenerAnnotations.size()])));
            }

            if (hasClassLevelListeners) {
                RabbitHandler rabbitHandler = (RabbitHandler) AnnotationUtils.findAnnotation(method, RabbitHandler.class);
                if (rabbitHandler != null) {
                    multiMethods.add(method);
                }
            }

        }, ReflectionUtils.USER_DECLARED_METHODS);
        return methods.isEmpty() && multiMethods.isEmpty() ? MultiRabbitListenerAnnotationBeanPostProcessor.TypeMetadata.EMPTY : new MultiRabbitListenerAnnotationBeanPostProcessor.TypeMetadata((MultiRabbitListenerAnnotationBeanPostProcessor.ListenerMethod[])methods.toArray(new MultiRabbitListenerAnnotationBeanPostProcessor.ListenerMethod[methods.size()]), (Method[])multiMethods.toArray(new Method[multiMethods.size()]), (RabbitListener[])classLevelListeners.toArray(new RabbitListener[classLevelListeners.size()]));
    }

    private Collection<RabbitListener> findListenerAnnotations(AnnotatedElement element) {
        return (Collection) MergedAnnotations.from(element, MergedAnnotations.SearchStrategy.TYPE_HIERARCHY).stream(RabbitListener.class).map((ann) -> {
            return (RabbitListener)ann.synthesize();
        }).collect(Collectors.toList());
    }

    private void processMultiMethodListeners(RabbitListener[] classLevelListeners, Method[] multiMethods, Object bean, String beanName) {
        List<Method> checkedMethods = new ArrayList();
        Method defaultMethod = null;
        Method[] var7 = multiMethods;
        int var8 = multiMethods.length;

        int var9;
        for(var9 = 0; var9 < var8; ++var9) {
            Method method = var7[var9];
            Method checked = this.checkProxy(method, bean);
            if (((RabbitHandler)AnnotationUtils.findAnnotation(method, RabbitHandler.class)).isDefault()) {
                String s11 = defaultMethod.toString();
                Assert.state(defaultMethod == null, () -> {
                    return "Only one @RabbitHandler can be marked 'isDefault', found: " + s11 + " and " + method.toString();
                });
                defaultMethod = checked;
            }

            checkedMethods.add(checked);
        }

        RabbitListener[] var15 = classLevelListeners;
        var8 = classLevelListeners.length;

        for(var9 = 0; var9 < var8; ++var9) {
            RabbitListener classLevelListener = var15[var9];
            MultiMethodRabbitListenerEndpoint endpoint = new MultiMethodRabbitListenerEndpoint(checkedMethods, defaultMethod, bean);
            this.processListener(endpoint, classLevelListener, bean, bean.getClass(), beanName);
        }

    }

    protected void processAmqpListener(RabbitListener rabbitListener, Method method, Object bean, String beanName) {
        Method methodToUse = this.checkProxy(method, bean);
        MethodRabbitListenerEndpoint endpoint = new MethodRabbitListenerEndpoint();
        endpoint.setMethod(methodToUse);
        this.processListener(endpoint, rabbitListener, bean, methodToUse, beanName);
    }

    private Method checkProxy(Method methodArg, Object bean) {
        Method method = methodArg;
        if (AopUtils.isJdkDynamicProxy(bean)) {
            try {
                method = bean.getClass().getMethod(method.getName(), method.getParameterTypes());
                Class<?>[] proxiedInterfaces = ((Advised)bean).getProxiedInterfaces();
                Class[] var5 = proxiedInterfaces;
                int var6 = proxiedInterfaces.length;
                int var7 = 0;

                while(var7 < var6) {
                    Class iface = var5[var7];

                    try {
                        method = iface.getMethod(method.getName(), method.getParameterTypes());
                        break;
                    } catch (NoSuchMethodException var10) {
                        ++var7;
                    }
                }
            } catch (SecurityException var11) {
                ReflectionUtils.handleReflectionException(var11);
            } catch (NoSuchMethodException var12) {
                throw new IllegalStateException(String.format("@RabbitListener method '%s' found on bean target class '%s', but not found in any interface(s) for a bean JDK proxy. Either pull the method up to an interface or switch to subclass (CGLIB) proxies by setting proxy-target-class/proxyTargetClass attribute to 'true'", methodArg.getName(), methodArg.getDeclaringClass().getSimpleName()), var12);
            }
        }

        return method;
    }

    protected void processListener(MethodRabbitListenerEndpoint endpoint, RabbitListener rabbitListener, Object bean, Object target, String beanName) {
        endpoint.setBean(bean);
        endpoint.setMessageHandlerMethodFactory(this.messageHandlerMethodFactory);
        endpoint.setId(this.getEndpointId(rabbitListener));
        endpoint.setQueueNames(this.resolveQueues(rabbitListener));
        endpoint.setConcurrency(this.resolveExpressionAsStringOrInteger(rabbitListener.concurrency(), "concurrency"));
        endpoint.setBeanFactory(this.beanFactory);
        endpoint.setReturnExceptions(this.resolveExpressionAsBoolean(rabbitListener.returnExceptions()));
        Object errorHandler = this.resolveExpression(rabbitListener.errorHandler());
        String errorHandlerBeanName;
        if (errorHandler instanceof RabbitListenerErrorHandler) {
            endpoint.setErrorHandler((RabbitListenerErrorHandler)errorHandler);
        } else {
            if (!(errorHandler instanceof String)) {
                throw new IllegalStateException("error handler mut be a bean name or RabbitListenerErrorHandler, not a " + errorHandler.getClass().toString());
            }

            errorHandlerBeanName = (String)errorHandler;
            if (StringUtils.hasText(errorHandlerBeanName)) {
                endpoint.setErrorHandler((RabbitListenerErrorHandler)this.beanFactory.getBean(errorHandlerBeanName, RabbitListenerErrorHandler.class));
            }
        }

        errorHandlerBeanName = rabbitListener.group();
        if (StringUtils.hasText(errorHandlerBeanName)) {
            Object resolvedGroup = this.resolveExpression(errorHandlerBeanName);
            if (resolvedGroup instanceof String) {
                endpoint.setGroup((String)resolvedGroup);
            }
        }

        String autoStartup = rabbitListener.autoStartup();
        if (StringUtils.hasText(autoStartup)) {
            endpoint.setAutoStartup(this.resolveExpressionAsBoolean(autoStartup));
        }

        endpoint.setExclusive(rabbitListener.exclusive());
        String priority = this.resolveExpressionAsString(rabbitListener.priority(), "priority");
        if (StringUtils.hasText(priority)) {
            try {
                endpoint.setPriority(Integer.valueOf(priority));
            } catch (NumberFormatException var11) {
                throw new BeanInitializationException("Invalid priority value for " + rabbitListener + " (must be an integer)", var11);
            }
        }

        this.resolveExecutor(endpoint, rabbitListener, target, beanName);
        this.resolveAdmin(endpoint, rabbitListener, target);
        this.resolveAckMode(endpoint, rabbitListener);
        this.resolvePostProcessor(endpoint, rabbitListener, target, beanName);
        RabbitListenerContainerFactory<?> factory = this.resolveContainerFactory(rabbitListener, target, beanName);

        /**
         * 改造点二
         * 该配置会将指定的rabbitmq源在启动时排消息监听器自动启动 rabbitmq.exclude-rabbits
         * 该配置会将指定的queueName在启动时排消息监听器自动启动 rabbitmq.exclude-queue-names
         */
        String excludeRabbits = this.configurableEnvironment.getProperty("rabbitmq.exclude-rabbits");
        if (excludeRabbits!=null && !"".equals(excludeRabbits)) {
            String[] excludeRabbitsArr = excludeRabbits.trim().split(",");
            String containerFactory = rabbitListener.containerFactory();
            if (containElement(excludeRabbitsArr, containerFactory)) {
                logger.warn(String.format("[MultiRabbitMQ] RabbitListener containerFactory [%s] and queueNames %s autoStartup has been disabled by config[rabbitmq.exclude-rabbits].",
                        containerFactory, endpoint.getQueueNames().toString()));
                endpoint.setAutoStartup(false);
            }
        }
        String excludeQueueNames = this.configurableEnvironment.getProperty("rabbitmq.exclude-queue-names");
        if (excludeQueueNames!=null && !"".equals(excludeQueueNames)) {
            String[] excludeQueueNamesArr = excludeQueueNames.trim().split(",");
            String[] queues = rabbitListener.queues();
            if (containElementInArray(excludeQueueNamesArr, queues) && endpoint.getAutoStartup()) {
                logger.warn(String.format("[MultiRabbitMQ] RabbitListener containerFactory [%s] and queueNames %s autoStartup has been disabled by config[rabbitmq.exclude-queue-names].",
                        rabbitListener.containerFactory(), endpoint.getQueueNames().toString()));
                endpoint.setAutoStartup(false);
            }
        }

        /**
         * 改造点三
         * 展示autoStartup为true的监听
         */
        boolean showActiveListenerDetails = Boolean.parseBoolean(this.configurableEnvironment.getProperty("rabbitmq.show-active-listener-details"));
        if (showActiveListenerDetails && endpoint.getAutoStartup()) {
            logger.info(String.format("[MultiRabbitMQ] RabbitListener containerFactory [%s] and queueNames %s is listening.", rabbitListener.containerFactory(), endpoint.getQueueNames().toString()));
        }

        this.registrar.registerEndpoint(endpoint, factory);
    }

    /**
     * 数组是否包含某个元素
     * @param sources
     * @param source
     * @return
     */
    private boolean containElement(String[] sources, String source) {
        for (String s : sources) {
            if (source.equals(s)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 数组是否包含另一个数组中的某个元素
     * @param sources
     * @param targets
     * @return
     */
    private boolean containElementInArray(String[] sources, String[] targets) {
        for (String s : sources) {
            if (containElement(targets, s)) {
                return true;
            }
        }
        return false;
    }


    private void resolveAckMode(MethodRabbitListenerEndpoint endpoint, RabbitListener rabbitListener) {
        String ackModeAttr = rabbitListener.ackMode();
        if (StringUtils.hasText(ackModeAttr)) {
            Object ackMode = this.resolveExpression(ackModeAttr);
            if (ackMode instanceof String) {
                endpoint.setAckMode(AcknowledgeMode.valueOf((String)ackMode));
            } else if (ackMode instanceof AcknowledgeMode) {
                endpoint.setAckMode((AcknowledgeMode)ackMode);
            } else {
                Assert.isNull(ackMode, "ackMode must resolve to a String or AcknowledgeMode");
            }
        }

    }

    private void resolveAdmin(MethodRabbitListenerEndpoint endpoint, RabbitListener rabbitListener, Object adminTarget) {
        String rabbitAdmin = this.resolveExpressionAsString(rabbitListener.admin(), "admin");
        if (StringUtils.hasText(rabbitAdmin)) {
            Assert.state(this.beanFactory != null, "BeanFactory must be set to resolve RabbitAdmin by bean name");

            try {
                endpoint.setAdmin((AmqpAdmin)this.beanFactory.getBean(rabbitAdmin, RabbitAdmin.class));
            } catch (NoSuchBeanDefinitionException var6) {
                throw new BeanInitializationException("Could not register rabbit listener endpoint on [" + adminTarget + "], no " + RabbitAdmin.class.getSimpleName() + " with id '" + rabbitAdmin + "' was found in the application context", var6);
            }
        }

    }

    @Nullable
    private RabbitListenerContainerFactory<?> resolveContainerFactory(RabbitListener rabbitListener, Object factoryTarget, String beanName) {
        RabbitListenerContainerFactory<?> factory = null;
//        String containerFactoryBeanName = this.resolveExpressionAsString(rabbitListener.containerFactory(), "containerFactory");
        /*
         * 此处对RabbitListener注解上的containerFactory属性进行改造，适配多个RabbitMQ源的写法
         * containerFactory：此处只需要写RabbitMQ实例的名称，如：master/second 等待
         * @see com.hexgon.proj.mq.config.MultiRabbitAutoConfiguration
         * @RabbitListener(queues = "vipQueue", containerFactory = "second")
         * @author XuZhibin
         */
        String containerFactory = rabbitListener.containerFactory();
        if ("".equalsIgnoreCase(rabbitListener.containerFactory())) {
            // 当没有配置 containerFactory 时取默认RabbitMQ源
            containerFactory = this.configurableEnvironment.getProperty("rabbitmq.default-rabbit");
        }
        String rlcfName = containerFactory + "RabbitListenerContainerFactory";
        String containerFactoryBeanName = this.resolveExpressionAsString( rlcfName,"containerFactory");
        if (StringUtils.hasText(containerFactoryBeanName)) {
            this.assertBeanFactory();

            try {
                factory = (RabbitListenerContainerFactory)this.beanFactory.getBean(containerFactoryBeanName, RabbitListenerContainerFactory.class);
            } catch (NoSuchBeanDefinitionException var7) {
                throw new BeanInitializationException(this.noBeanFoundMessage(factoryTarget, beanName, containerFactoryBeanName, RabbitListenerContainerFactory.class), var7);
            }
        }

        return factory;
    }

    private void resolveExecutor(MethodRabbitListenerEndpoint endpoint, RabbitListener rabbitListener, Object execTarget, String beanName) {
        String execBeanName = this.resolveExpressionAsString(rabbitListener.executor(), "executor");
        if (StringUtils.hasText(execBeanName)) {
            this.assertBeanFactory();

            try {
                endpoint.setTaskExecutor((TaskExecutor)this.beanFactory.getBean(execBeanName, TaskExecutor.class));
            } catch (NoSuchBeanDefinitionException var7) {
                throw new BeanInitializationException(this.noBeanFoundMessage(execTarget, beanName, execBeanName, TaskExecutor.class), var7);
            }
        }

    }

    private void resolvePostProcessor(MethodRabbitListenerEndpoint endpoint, RabbitListener rabbitListener, Object target, String beanName) {
        String ppBeanName = this.resolveExpressionAsString(rabbitListener.replyPostProcessor(), "replyPostProcessor");
        if (StringUtils.hasText(ppBeanName)) {
            this.assertBeanFactory();

            try {
                endpoint.setReplyPostProcessor((ReplyPostProcessor)this.beanFactory.getBean(ppBeanName, ReplyPostProcessor.class));
            } catch (NoSuchBeanDefinitionException var7) {
                throw new BeanInitializationException(this.noBeanFoundMessage(target, beanName, ppBeanName, ReplyPostProcessor.class), var7);
            }
        }

    }

    protected void assertBeanFactory() {
        Assert.state(this.beanFactory != null, "BeanFactory must be set to obtain container factory by bean name");
    }

    protected String noBeanFoundMessage(Object target, String listenerBeanName, String requestedBeanName, Class<?> expectedClass) {
        return "Could not register rabbit listener endpoint on [" + target + "] for bean " + listenerBeanName + ", no '" + expectedClass.getSimpleName() + "' with id '" + requestedBeanName + "' was found in the application context";
    }

    private String getEndpointId(RabbitListener rabbitListener) {
        return StringUtils.hasText(rabbitListener.id()) ? this.resolveExpressionAsString(rabbitListener.id(), "id") : "org.springframework.amqp.rabbit.RabbitListenerEndpointContainer#" + this.counter.getAndIncrement();
    }

    private String[] resolveQueues(RabbitListener rabbitListener) {
        String[] queues = rabbitListener.queues();
        QueueBinding[] bindings = rabbitListener.bindings();
        org.springframework.amqp.rabbit.annotation.Queue[] queuesToDeclare = rabbitListener.queuesToDeclare();
        List<String> result = new ArrayList();
        int i;
        if (queues.length > 0) {
            for(i = 0; i < queues.length; ++i) {
                this.resolveAsString(this.resolveExpression(queues[i]), result, true, "queues");
            }
        }

        if (queuesToDeclare.length > 0) {
            if (queues.length > 0) {
                throw new BeanInitializationException("@RabbitListener can have only one of 'queues', 'queuesToDeclare', or 'bindings'");
            }

            for(i = 0; i < queuesToDeclare.length; ++i) {
                result.add(this.declareQueue(queuesToDeclare[i]));
            }
        }

        if (bindings.length > 0) {
            if (queues.length <= 0 && queuesToDeclare.length <= 0) {
                return this.registerBeansForDeclaration(rabbitListener);
            } else {
                throw new BeanInitializationException("@RabbitListener can have only one of 'queues', 'queuesToDeclare', or 'bindings'");
            }
        } else {
            return (String[])result.toArray(new String[result.size()]);
        }
    }

    private void resolveAsString(Object resolvedValue, List<String> result, boolean canBeQueue, String what) {
        Object resolvedValueToUse = resolvedValue;
        if (resolvedValue instanceof String[]) {
            resolvedValueToUse = Arrays.asList((String[])((String[])resolvedValue));
        }

        if (canBeQueue && resolvedValueToUse instanceof org.springframework.amqp.core.Queue) {
            result.add(((org.springframework.amqp.core.Queue)resolvedValueToUse).getName());
        } else if (resolvedValueToUse instanceof String) {
            result.add((String)resolvedValueToUse);
        } else {
            if (!(resolvedValueToUse instanceof Iterable)) {
                throw new IllegalArgumentException(String.format("@RabbitListener." + what + " can't resolve '%s' as a String[] or a String " + (canBeQueue ? "or a Queue" : ""), resolvedValue));
            }

            Iterator var6 = ((Iterable)resolvedValueToUse).iterator();

            while(var6.hasNext()) {
                Object object = var6.next();
                this.resolveAsString(object, result, canBeQueue, what);
            }
        }

    }

    private String[] registerBeansForDeclaration(RabbitListener rabbitListener) {
        List<String> queues = new ArrayList();
        if (this.beanFactory instanceof ConfigurableBeanFactory) {
            QueueBinding[] var3 = rabbitListener.bindings();
            int var4 = var3.length;

            for(int var5 = 0; var5 < var4; ++var5) {
                QueueBinding binding = var3[var5];
                String queueName = this.declareQueue(binding.value());
                queues.add(queueName);
                this.declareExchangeAndBinding(binding, queueName);
            }
        }

        return (String[])queues.toArray(new String[queues.size()]);
    }

    private String declareQueue(Queue bindingQueue) {
        String queueName = (String)this.resolveExpression(bindingQueue.value());
        boolean isAnonymous = false;
        if (!StringUtils.hasText(queueName)) {
            queueName = Base64UrlNamingStrategy.DEFAULT.generateName();
            isAnonymous = true;
        }

        org.springframework.amqp.core.Queue queue = new org.springframework.amqp.core.Queue(queueName, this.resolveExpressionAsBoolean(bindingQueue.durable(), !isAnonymous), this.resolveExpressionAsBoolean(bindingQueue.exclusive(), isAnonymous), this.resolveExpressionAsBoolean(bindingQueue.autoDelete(), isAnonymous), this.resolveArguments(bindingQueue.arguments()));
        queue.setIgnoreDeclarationExceptions(this.resolveExpressionAsBoolean(bindingQueue.ignoreDeclarationExceptions()));
        ((ConfigurableBeanFactory)this.beanFactory).registerSingleton(queueName + ++this.increment, queue);
        if (bindingQueue.admins().length > 0) {
            queue.setAdminsThatShouldDeclare((Object[])bindingQueue.admins());
        }

        queue.setShouldDeclare(this.resolveExpressionAsBoolean(bindingQueue.declare()));
        return queueName;
    }

    private void declareExchangeAndBinding(QueueBinding binding, String queueName) {
        Exchange bindingExchange = binding.exchange();
        String exchangeName = this.resolveExpressionAsString(bindingExchange.value(), "@Exchange.exchange");
        Assert.isTrue(StringUtils.hasText(exchangeName), () -> {
            return "Exchange name required; binding queue " + queueName;
        });
        String exchangeType = this.resolveExpressionAsString(bindingExchange.type(), "@Exchange.type");
        ExchangeBuilder exchangeBuilder = new ExchangeBuilder(exchangeName, exchangeType);
        if (this.resolveExpressionAsBoolean(bindingExchange.autoDelete())) {
            exchangeBuilder.autoDelete();
        }

        if (this.resolveExpressionAsBoolean(bindingExchange.internal())) {
            exchangeBuilder.internal();
        }

        if (this.resolveExpressionAsBoolean(bindingExchange.delayed())) {
            exchangeBuilder.delayed();
        }

        if (this.resolveExpressionAsBoolean(bindingExchange.ignoreDeclarationExceptions())) {
            exchangeBuilder.ignoreDeclarationExceptions();
        }

        if (!this.resolveExpressionAsBoolean(bindingExchange.declare())) {
            exchangeBuilder.suppressDeclaration();
        }

        if (bindingExchange.admins().length > 0) {
            exchangeBuilder.admins((Object[])bindingExchange.admins());
        }

        Map<String, Object> arguments = this.resolveArguments(bindingExchange.arguments());
        if (!CollectionUtils.isEmpty(arguments)) {
            exchangeBuilder.withArguments(arguments);
        }

        org.springframework.amqp.core.Exchange exchange = exchangeBuilder.durable(this.resolveExpressionAsBoolean(bindingExchange.durable())).build();
        ((ConfigurableBeanFactory)this.beanFactory).registerSingleton(exchangeName + ++this.increment, exchange);
        this.registerBindings(binding, queueName, exchangeName, exchangeType);
    }

    private void registerBindings(QueueBinding binding, String queueName, String exchangeName, String exchangeType) {
        Object routingKeys;
        if (!exchangeType.equals("fanout") && binding.key().length != 0) {
            int length = binding.key().length;
            routingKeys = new ArrayList();

            for(int i = 0; i < length; ++i) {
                this.resolveAsString(this.resolveExpression(binding.key()[i]), (List)routingKeys, false, "@QueueBinding.key");
            }
        } else {
            routingKeys = Collections.singletonList("");
        }

        Map<String, Object> bindingArguments = this.resolveArguments(binding.arguments());
        boolean bindingIgnoreExceptions = this.resolveExpressionAsBoolean(binding.ignoreDeclarationExceptions());
        boolean declare = this.resolveExpressionAsBoolean(binding.declare());

        Binding actualBinding;
        for(Iterator var9 = ((List)routingKeys).iterator(); var9.hasNext(); ((ConfigurableBeanFactory)this.beanFactory).registerSingleton(exchangeName + "." + queueName + ++this.increment, actualBinding)) {
            String routingKey = (String)var9.next();
            actualBinding = new Binding(queueName, Binding.DestinationType.QUEUE, exchangeName, routingKey, bindingArguments);
            actualBinding.setIgnoreDeclarationExceptions(bindingIgnoreExceptions);
            actualBinding.setShouldDeclare(declare);
            if (binding.admins().length > 0) {
                actualBinding.setAdminsThatShouldDeclare((Object[])binding.admins());
            }
        }

    }

    private Map<String, Object> resolveArguments(Argument[] arguments) {
        Map<String, Object> map = new HashMap();
        Argument[] var3 = arguments;
        int var4 = arguments.length;

        for(int var5 = 0; var5 < var4; ++var5) {
            Argument arg = var3[var5];
            String key = this.resolveExpressionAsString(arg.name(), "@Argument.name");
            if (StringUtils.hasText(key)) {
                Object value = this.resolveExpression(arg.value());
                Object type = this.resolveExpression(arg.type());
                Class typeClass;
                String typeName;
                if (type instanceof Class) {
                    typeClass = (Class)type;
                    typeName = typeClass.getName();
                } else {
                    Assert.isTrue(type instanceof String, () -> {
                        return "Type must resolve to a Class or String, but resolved to [" + type.getClass().getName() + "]";
                    });
                    typeName = (String)type;

                    try {
                        typeClass = ClassUtils.forName(typeName, this.beanClassLoader);
                    } catch (Exception var13) {
                        throw new IllegalStateException("Could not load class", var13);
                    }
                }

                this.addToMap(map, key, value, typeClass, typeName);
            } else if (this.logger.isDebugEnabled()) {
                this.logger.debug("@Argument ignored because the name resolved to an empty String");
            }
        }

        return map.size() < 1 ? null : map;
    }

    private void addToMap(Map<String, Object> map, String key, Object value, Class<?> typeClass, String typeName) {
        if (value.getClass().getName().equals(typeName)) {
            if (typeClass.equals(String.class) && !StringUtils.hasText((String)value)) {
                this.putEmpty(map, key);
            } else {
                map.put(key, value);
            }
        } else if (value instanceof String && !StringUtils.hasText((String)value)) {
            this.putEmpty(map, key);
        } else {
            if (!CONVERSION_SERVICE.canConvert(value.getClass(), typeClass)) {
                throw new IllegalStateException("Cannot convert from " + value.getClass().getName() + " to " + typeName);
            }

            map.put(key, CONVERSION_SERVICE.convert(value, typeClass));
        }

    }

    private void putEmpty(Map<String, Object> map, String key) {
        if (this.emptyStringArguments.contains(key)) {
            map.put(key, "");
        } else {
            map.put(key, (Object)null);
        }

    }

    private boolean resolveExpressionAsBoolean(String value) {
        return this.resolveExpressionAsBoolean(value, false);
    }

    private boolean resolveExpressionAsBoolean(String value, boolean defaultValue) {
        Object resolved = this.resolveExpression(value);
        if (resolved instanceof Boolean) {
            return (Boolean)resolved;
        } else if (resolved instanceof String) {
            String s = (String)resolved;
            return StringUtils.hasText(s) ? Boolean.parseBoolean(s) : defaultValue;
        } else {
            return defaultValue;
        }
    }

    private String resolveExpressionAsString(String value, String attribute) {
        Object resolved = this.resolveExpression(value);
        if (resolved instanceof String) {
            return (String)resolved;
        } else {
            throw new IllegalStateException("The [" + attribute + "] must resolve to a String. Resolved to [" + resolved.getClass() + "] for [" + value + "]");
        }
    }

    private String resolveExpressionAsStringOrInteger(String value, String attribute) {
        if (!StringUtils.hasLength(value)) {
            return null;
        } else {
            Object resolved = this.resolveExpression(value);
            if (resolved instanceof String) {
                return (String)resolved;
            } else if (resolved instanceof Integer) {
                return resolved.toString();
            } else {
                throw new IllegalStateException("The [" + attribute + "] must resolve to a String. Resolved to [" + resolved.getClass() + "] for [" + value + "]");
            }
        }
    }

    private Object resolveExpression(String value) {
        String resolvedValue = this.resolve(value);
        return this.resolver.evaluate(resolvedValue, this.expressionContext);
    }

    private String resolve(String value) {
        return this.beanFactory != null && this.beanFactory instanceof ConfigurableBeanFactory ? ((ConfigurableBeanFactory)this.beanFactory).resolveEmbeddedValue(value) : value;
    }

    private static class BytesToStringConverter implements Converter<byte[], String> {
        private final Charset charset;

        BytesToStringConverter(Charset charset) {
            this.charset = charset;
        }

        public String convert(byte[] source) {
            return new String(source, this.charset);
        }
    }

    private static class ListenerMethod {
        final Method method;
        final RabbitListener[] annotations;

        ListenerMethod(Method method, RabbitListener[] annotations) {
            this.method = method;
            this.annotations = annotations;
        }
    }

    private static class TypeMetadata {
        final MultiRabbitListenerAnnotationBeanPostProcessor.ListenerMethod[] listenerMethods;
        final Method[] handlerMethods;
        final RabbitListener[] classAnnotations;
        static final MultiRabbitListenerAnnotationBeanPostProcessor.TypeMetadata EMPTY = new MultiRabbitListenerAnnotationBeanPostProcessor.TypeMetadata();

        private TypeMetadata() {
            this.listenerMethods = new MultiRabbitListenerAnnotationBeanPostProcessor.ListenerMethod[0];
            this.handlerMethods = new Method[0];
            this.classAnnotations = new RabbitListener[0];
        }

        TypeMetadata(MultiRabbitListenerAnnotationBeanPostProcessor.ListenerMethod[] methods, Method[] multiMethods, RabbitListener[] classLevelListeners) {
            this.listenerMethods = methods;
            this.handlerMethods = multiMethods;
            this.classAnnotations = classLevelListeners;
        }
    }

    private class RabbitHandlerMethodFactoryAdapter implements MessageHandlerMethodFactory {
        private MessageHandlerMethodFactory factory;

        RabbitHandlerMethodFactoryAdapter() {
        }

        public void setMessageHandlerMethodFactory(MessageHandlerMethodFactory rabbitHandlerMethodFactory1) {
            this.factory = rabbitHandlerMethodFactory1;
        }

        public InvocableHandlerMethod createInvocableHandlerMethod(Object bean, Method method) {
            return this.getFactory().createInvocableHandlerMethod(bean, method);
        }

        private MessageHandlerMethodFactory getFactory() {
            if (this.factory == null) {
                this.factory = this.createDefaultMessageHandlerMethodFactory();
            }

            return this.factory;
        }

        private MessageHandlerMethodFactory createDefaultMessageHandlerMethodFactory() {
            DefaultMessageHandlerMethodFactory defaultFactory = new DefaultMessageHandlerMethodFactory();
            defaultFactory.setBeanFactory(MultiRabbitListenerAnnotationBeanPostProcessor.this.beanFactory);
            DefaultConversionService conversionService = new DefaultConversionService();
            conversionService.addConverter(new MultiRabbitListenerAnnotationBeanPostProcessor.BytesToStringConverter(MultiRabbitListenerAnnotationBeanPostProcessor.this.charset));
            defaultFactory.setConversionService(conversionService);
            defaultFactory.afterPropertiesSet();
            return defaultFactory;
        }
    }

}
